#!/usr/bin/env python3
#
# Validates that 3 data sources agree about the structure of Zulip's events API:
#
# * Node fixtures for the server_events_dispatch.js tests.
# * OpenAPI definitions in zerver/openapi/zulip.yaml
# * The schemas defined in zerver/lib/events_schema.py used for the
#   Zulip server's test suite.
#
# We compare the Python and OpenAPI schemas by converting the OpenAPI data
# into the event_schema style of types and the diffing the schemas.
import argparse
import os
import subprocess
import sys
from collections.abc import Callable
from typing import Any

import orjson

TOOLS_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.dirname(TOOLS_DIR))
ROOT_DIR = os.path.dirname(TOOLS_DIR)

EVENTS_JS = "web/tests/lib/events.cjs"

# check for the venv
from tools.lib import sanity_check

sanity_check.check_venv(__file__)

USAGE = """

    This program reads in fixture data for our
    node tests, and then it validates the fixture
    data with checkers from event_schema.py (which
    are the same Python functions we use to validate
    events in test_events.py).

    It currently takes no arguments.
"""

parser = argparse.ArgumentParser(usage=USAGE)
parser.parse_args()

# We can eliminate the django dependency in event_schema,
# but unfortunately it"s coupled to modules like validate.py
# and topic.py.
import django

os.environ["DJANGO_SETTINGS_MODULE"] = "zproject.test_settings"
django.setup()

from zerver.lib import event_schema

make_checker = event_schema.__dict__["make_checker"]


def get_event_checker(event: dict[str, Any]) -> Callable[[str, dict[str, Any]], None]:
    # Follow the naming convention to find the event checker.
    # Start by grabbing the event type.
    name = event["type"]

    # Handle things like AttachmentRemoveEvent
    if "op" in event:
        name += "_" + event["op"].title()

    # Change to CamelCase
    name = name.replace("_", " ").title().replace(" ", "")

    # Use EventModernPresence type to check "presence" events
    if name == "Presence":
        name = "Modern" + name

    # And add the prefix.
    name = "Event" + name

    if not hasattr(event_schema, name):
        raise ValueError(f"We could not find {name} in event_schemas.py")

    return make_checker(getattr(event_schema, name))


def check_event(name: str, event: dict[str, Any]) -> None:
    event["id"] = 1
    checker = get_event_checker(event)
    try:
        checker(name, event)
    except AssertionError:
        print(f"\n{EVENTS_JS} has bad data for {name}:\n\n")
        raise


def read_fixtures() -> dict[str, Any]:
    cmd = [
        "node",
        os.path.join(TOOLS_DIR, "node_lib/dump_fixtures.js"),
    ]
    schema = subprocess.check_output(cmd)
    return orjson.loads(schema)


def verify_fixtures_are_sorted(names: list[str]) -> None:
    for i in range(1, len(names)):
        if names[i] < names[i - 1]:
            raise Exception(
                f"""
                Please keep your fixtures in order within
                your events.cjs file.  The following
                key is out of order

                {names[i]}
                """
            )


def run() -> None:
    fixtures = read_fixtures()
    verify_fixtures_are_sorted(list(fixtures.keys()))
    for name, event in fixtures.items():
        check_event(name, event)
    print(f"Successfully checked {len(fixtures)} fixtures. All tests passed.")


if __name__ == "__main__":
    run()
